/** 
    Specific lom value types.
*/
#ifndef LOMVALUEHH
#define LOMVALUEHH
#include "lom-typedefs.hh"
#include "lom-errors.hh"
#include "lom-value-factory.hh"


namespace lom
{
    
    class LomValue
    {
    protected:
	LomValue(){}
	LomValue( const LomValue& ){}

    public:
	virtual ~LomValue(){}
	
	virtual EDataType getDataType() const = 0;
    
	virtual LomValue* clone(ILomValueFactory* factory) const {
	    LomValue* retval = factory->create( getDataType() );
	    *retval = *this;
	    return retval;
	}
	virtual void resetLomValue() {}
	//It is an error if the types don't match for these types
	//If you want a marshalling layer, you need to build it yo-self.
	virtual bool operator==( const LomValue& other ) const = 0;
	virtual bool operator<( const LomValue& other ) const = 0;
	virtual LomValue& operator=( const LomValue& other ) = 0;
    
	virtual bool getBool() const { throw DatatypeMismatch(); }
	virtual bool& getBool() { throw DatatypeMismatch(); }
	virtual void set( bool ) { throw DatatypeMismatch(); }
    
	virtual double getDouble() const { throw DatatypeMismatch(); }
	virtual double& getDouble() { throw DatatypeMismatch(); }
	virtual void set(double ) { throw DatatypeMismatch(); }
	
	virtual const licada::TDoubleVector& getDoubleV() const { 
	    throw DatatypeMismatch(); }
	virtual licada::TDoubleVector& getDoubleV() { 
	    throw DatatypeMismatch(); }
	virtual void set( const licada::TDoubleVector& ) { 
	    throw DatatypeMismatch(); }

	virtual licada::int64 getInt64() const { throw DatatypeMismatch(); }
	virtual licada::int64& getInt64() { throw DatatypeMismatch(); }
	virtual void set( licada::int64 ) { throw DatatypeMismatch(); }
	
	virtual const licada::TInt64Vector& getInt64V() const { 
	    throw DatatypeMismatch(); }
	virtual licada::TInt64Vector& getInt64V() { 
	    throw DatatypeMismatch(); }
	virtual void set( const licada::TInt64Vector& ) { 
	    throw DatatypeMismatch(); }
    
	virtual const licada::TString& getString() const { 
	    throw DatatypeMismatch(); }
	virtual licada::TString& getString() { 
	    throw DatatypeMismatch(); }
	virtual void set( const licada::TString& ) { throw DatatypeMismatch(); }
	
	virtual const licada::TStringVector& getStringV() const { 
	    throw DatatypeMismatch(); }
	virtual licada::TStringVector& getStringV() { 
	    throw DatatypeMismatch(); }
	virtual void set( const licada::TStringVector& ) { 
	    throw DatatypeMismatch(); }
    
	//These will add a reference to their objects to their objects
	virtual THandle getHandle() const { throw DatatypeMismatch(); }
	virtual void set(THandle ) { throw DatatypeMismatch(); }

	virtual const THandleVector& getHandleV() const { 
	    throw DatatypeMismatch(); }
	virtual void set( const THandleVector& ) const { 
	    throw DatatypeMismatch(); }

 	virtual const licada::TDataVector& getDataV() const {
	    throw DatatypeMismatch(); }
 	virtual licada::TDataVector& getDataV() { 
	    throw DatatypeMismatch(); }
	virtual void set( const licada::TDataVector& ) const { 
	    throw DatatypeMismatch(); }
    };
  

    template<typename TType, EDataType TTypeName >
    class LomSimpleValueType : public LomValue
    {
    protected:
	typedef LomSimpleValueType<TType, TTypeName> TMyType;
	TType data;
    public:

	LomSimpleValueType(){}
	
	LomSimpleValueType( const TType& _data )
	    : data( _data ){}
	
	LomSimpleValueType( const TMyType& other )
	    : LomValue( other ), data( other.data ){}
	
	TMyType& operator=( const TMyType& other ) {
	    if ( this != &other ) data = other.data;
	    return *this; 
	}
      
	virtual EDataType getDataType() const { return TTypeName; }
	
	virtual bool operator==( const LomValue& _other ) const {
	    const TMyType& other = dynamic_cast< const TMyType& >( _other );
	    return data == other.data;
	}
	virtual bool operator<( const LomValue& _other ) const {
	    const TMyType& other = dynamic_cast< const TMyType& >( _other );
	    return data < other.data;
	}
	virtual LomValue& operator=( const LomValue& _other ) {
	    const TMyType& other = dynamic_cast< const TMyType& >( _other );
	    data = other.data;
	    return *this;
	}

    protected:
    };

    class LomBool : public LomSimpleValueType< bool, EDataTypeBool >
    {
	typedef LomSimpleValueType< bool, EDataTypeBool > TBase;
    public:
	LomBool( bool val = false ) : TBase( val ) {}
	LomBool( const LomBool& other ) : TBase( other ){}
	virtual bool getBool() const { return data; }
	virtual bool& getBool() { return data; }
	virtual void set( bool val ) { data = val; }
    protected:
    };
    
    class LomDouble : public LomSimpleValueType< double, EDataTypeDouble >
    {
	typedef LomSimpleValueType< double, EDataTypeDouble > TBase;
    public:
	LomDouble( double val = 0 ) : TBase( val ){}
	LomDouble( const LomDouble& other ) : TBase( other ){}
	virtual double getDouble() const { return data; }
	virtual double& getDouble() { return data; }
	virtual void set( double val ) { data = val; }
    protected:
    };
    
    class LomDoubleV : public LomSimpleValueType< licada::TDoubleVector, EDataTypeDoubleV >
    {
	typedef LomSimpleValueType< licada::TDoubleVector, EDataTypeDoubleV > TBase;
    public:
	LomDoubleV(){}
	LomDoubleV( const licada::TDoubleVector& val ) : TBase( val ){}
	LomDoubleV( const LomDoubleV& other ) : TBase( other ){}
	virtual const licada::TDoubleVector& getDoubleV() const { return data; }
	virtual licada::TDoubleVector& getDoubleV() { return data; }
	virtual void set( const licada::TDoubleVector& val ) { data = val; }
    protected:
    };
    
    class LomInt64 : public LomSimpleValueType< licada::int64, EDataTypeInt64 >
    {
	typedef LomSimpleValueType< licada::int64, EDataTypeInt64 > TBase;
    public:
	LomInt64( licada::int64 val = 0 ) : TBase( val ){}
	LomInt64( const LomInt64& other ) : TBase( other ){}
	virtual licada::int64 getInt64() const { return data; }
	licada::int64& getInt64() { return data; }
	virtual void set( licada::int64 val ) { data = val; }
    protected:
    };
    
    class LomInt64V : public LomSimpleValueType< licada::TInt64Vector, EDataTypeInt64V >
    {
	typedef LomSimpleValueType< licada::TInt64Vector, EDataTypeInt64V > TBase;
    public:
	LomInt64V(){}
	LomInt64V( const licada::TInt64Vector& val ) : TBase( val ){}
	LomInt64V( const LomInt64V& other ) : TBase( other ){}
	virtual const licada::TInt64Vector& getInt64V() const { return data; }
	virtual licada::TInt64Vector& getInt64V() { return data; }
	virtual void set( const licada::TInt64Vector& val ) { data = val; }
    protected:
    };
    
    class LomString : public LomSimpleValueType< licada::TString, EDataTypeString >
    {
	typedef LomSimpleValueType< licada::TString, EDataTypeString > TBase;
    public:
	LomString(){}
	LomString( const licada::TString& val ) : TBase( val ){}
	LomString( const LomString& other ) : TBase( other ){}
	virtual const licada::TString& getString() const { return data; }
	virtual licada::TString& getString() { return data; }
	virtual void set( const licada::TString& val ) { data = val; }
    protected:
    };
    
    class LomStringV : public LomSimpleValueType< licada::TStringVector, EDataTypeStringV >
    {
	typedef LomSimpleValueType< licada::TStringVector, EDataTypeStringV > TBase;
    public:
	LomStringV(){}
	LomStringV( const licada::TStringVector& val ) : TBase( val ){}
	LomStringV( const LomStringV& other ) : TBase( other ){}
	virtual const licada::TStringVector& getStringV() const { return data; }
	virtual licada::TStringVector& getStringV() { return data; }
	virtual void set( const licada::TStringVector& val ) { data =val; }
    protected:
    };
    
    class LomHandle : public LomSimpleValueType< THandle, EDataTypeHandle >
    {
	typedef LomSimpleValueType< THandle, EDataTypeHandle > TBase;
	ILomInstanceManager* mgr;
    public:
	static void refData( THandle data, ILomInstanceManager* mgr);
	static void unrefData( THandle data, ILomInstanceManager* mgr );


	LomHandle( ILomInstanceManager* _mgr = NULL ) : mgr(_mgr){ data = 0; }
	LomHandle( THandle val, ILomInstanceManager* _mgr ) : TBase( val ), mgr( _mgr ){
	    refData( data, mgr );
	}
	LomHandle( const LomHandle& other ) : TBase( other ), mgr( other.mgr ) {
	    refData( data, mgr );
	}
	LomHandle& operator=(const LomHandle& other ) {
	    if ( this != &other ) {
		unrefData( data, mgr);
		data = other.data;
		refData( data, mgr );
	    }
	    return *this;
	}
		
	virtual ~LomHandle() { unrefData(data, mgr); }
	virtual void resetLomValue() { unrefData( data, mgr ); data = 0; }
	virtual THandle getHandle() const { return data; }
	virtual void set(THandle val) {
	    //Referencing before dereferencing protects against if the same handle
	    //is being passed in that I already own
	    refData( val, mgr );
	    unrefData( data, mgr );
	    data = val;
	}
	virtual LomValue& operator=( const LomValue& _other ) {
	    const LomHandle& other = dynamic_cast< const LomHandle& >( _other );
	    set( other.getHandle() );
	    return *this;
	}
    protected:
    };

    class LomHandleV : public LomSimpleValueType< THandleVector, EDataTypeHandleV >
    {
	typedef LomSimpleValueType< THandleVector, EDataTypeHandleV > TBase;
	ILomInstanceManager* mgr;
    public:
	static void refVec( const THandleVector& vec, ILomInstanceManager* mgr ) {
	    std::for_each( vec.begin(), vec.end()
			   , boost::bind( LomHandle::refData, _1, mgr) );
	}
	static void unrefVec( THandleVector& vec, ILomInstanceManager* mgr ) {
	    std::for_each( vec.begin(), vec.end()
			   , boost::bind( LomHandle::unrefData, _1, mgr) );
	    vec.clear();
	}
	
	LomHandleV( ILomInstanceManager* _mgr = NULL ) : mgr( _mgr ){}
	LomHandleV( const THandleVector& val, ILomInstanceManager* _mgr = NULL ) 
	    : TBase( val ), mgr( _mgr ) {
	    refVec( data, mgr );
	}
	LomHandleV( const LomHandleV& other, ILomInstanceManager* _mgr = NULL ) 
	    : TBase( other ), mgr( _mgr ) {
	    refVec( data, mgr );
	}
	LomHandleV& operator=( const LomHandleV& other ) {
	    if ( this != &other ) {
		unrefVec( data, mgr );
		data = other.data;
		refVec( data, mgr );
	    }
	    return *this;
	}
	virtual ~LomHandleV() { unrefVec( data, mgr ); }
	virtual void resetLomValue() { unrefVec( data, mgr ); }
	virtual const THandleVector& getHandleV() const { return data; }
	virtual void set( const THandleVector& val ) {
	    //Referencing before dereferencing protects against if the
	    //same handle is in both arrays, and I own the only handle
	    refVec( val, mgr );
	    unrefVec( data, mgr );
	    data = val;
	}
	virtual LomValue& operator=( const LomValue& _other ) {
	    const LomHandleV& other = dynamic_cast< const LomHandleV& >( _other );
	    set( other.getHandleV() );
	    return *this;
	}
    protected:
    };
    
    class LomDataV 
	: public LomSimpleValueType< licada::TDataVector, EDataTypeDataV >
    {
	typedef LomSimpleValueType< licada::TDataVector, EDataTypeDataV > TBase;
    public:
	LomDataV(){}
	LomDataV( const licada::TDataVector& val ) : TBase( val ){}
	LomDataV( const LomDataV& other ) : TBase( other ){}
	virtual const licada::TDataVector& getDataV() const { return data; }
	virtual licada::TDataVector& getDataV() { return data; }
	virtual void set( const licada::TDataVector& val) { data = val; }
    protected:
    };
}

#endif
